/*
 * Copyright 2008-2023 dexian.vip. All rights reserved.
 * Support: http://www.dexian.vip
 * License: http://www.dexian.vip/license
 */

package vip.dexian.core.base.service;

import vip.dexian.common.query.Filter;
import vip.dexian.common.query.Listable;
import vip.dexian.common.query.Page;
import vip.dexian.common.query.Pageable;
import vip.dexian.common.utils.FilterUtils;
import vip.dexian.common.utils.NumberUtils;
import vip.dexian.core.base.bean.BaseDTO;
import vip.dexian.core.base.bean.BaseEntity;
import vip.dexian.core.base.dao.BaseDao;
import vip.dexian.core.base.mapstruct.BaseEntityToDTOAssembler;
import vip.dexian.core.base.query.BaseQuery;
import vip.dexian.core.base.validation.BaseValidator;
import vip.dexian.core.exception.ServiceValidationException;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.beanutils.PropertyUtils;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.Assert;

import java.beans.PropertyDescriptor;
import java.io.Serializable;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;

/**
 * 业务逻辑基类。切勿把事务定义在整个类上，如果定义在整个类上，表示类中所有方法都要支持事务。并且继承该类的子类也都继承了该事务。
 *
 * @author 挺好的 2023年05月30日 19:34
 */
@Slf4j
public abstract class BaseServiceImpl<T extends BaseEntity <ID>, D extends BaseDTO <ID>, ID extends Serializable>
        extends BaseValidator implements BaseService <T, D, ID> {

    /**
     * 忽略更新的属性
     */
    private static final String[] UPDATE_IGNORE_PROPERTIES = new String[] {BaseEntity.CREATE_DATE_PROPERTY_NAME, BaseEntity.MODIFY_DATE_PROPERTY_NAME, BaseEntity.VERSION_PROPERTY_NAME, BaseEntity.IS_VALID_PROPERTY_NAME};

    /**
     * 删除操作忽略更新的属性
     */
    private static final String[] DELETE_IGNORE_PROPERTY = new String[] {BaseEntity.CREATE_DATE_PROPERTY_NAME, BaseEntity.MODIFY_DATE_PROPERTY_NAME, BaseEntity.VERSION_PROPERTY_NAME};

    /**
     * 最多删除多少条数据。最多50条
     */
    private static final int MAX_DELETE_COUNT = 50;

    /**
     * base Dao
     */
    @Autowired
    private BaseDao <T, ID> baseDao;

    /**
     * entity -> dto 转换器
     */
    @Autowired
    private BaseEntityToDTOAssembler <T, D, ID> baseAssembler;

    /**
     * 根据ID查找实体类，并转换为对应的DTO。该实体类处于托管状态。在同一个事务中，如果对该实体类进行属性修改，会发出update语句
     *
     * @param id
     *         ID
     *
     * @return 1. ID == null，返回null。2. 无法查找到数据，返回null。
     */
    @Override
    @Transactional (propagation = Propagation.SUPPORTS, readOnly = true)
    public D find (ID id) {
        T entity = this.baseDao.find(id);
        return this.baseAssembler.toDTO(entity);
    }


    /**
     * 根据ID查找实体类。如果id == null || 无法查找到对应的数据，将抛出异常。该实体类处于托管状态。在同一个事务中，如果对该实体类进行属性修改，会发出update语句。
     *
     * @param id
     *         实体类ID
     * @param args
     *         国际化消息占位
     *
     * @return 1. ID == null。2. 无法查找到数据，throws ServiceValidationException。
     *
     * @throws ServiceValidationException
     *         当id == null || 无法查找到数据
     */
    @Override
    @Transactional (propagation = Propagation.SUPPORTS, readOnly = true)
    public D findByValidationIfNull (ID id, Object... args) {
        T entity = this.baseDao.findByValidationIfNull(id, args);
        return this.baseAssembler.toDTO(entity);
    }

    /**
     * 根据条件查询数据
     *
     * @param filters
     *         搜索条件
     *
     * @return 条件为空，返回null
     */
    @Override
    @Transactional (propagation = Propagation.SUPPORTS, readOnly = true)
    public D find (List <Filter> filters) {
        T entity = this.baseDao.find(filters);

        return this.baseAssembler.toDTO(entity);
    }

    /**
     * 查询全部实体类。如果实体类继承自OrderEntity，按排序升序。否则按照ID降序
     *
     * @return 对应的实体列表
     */
    @Override
    @Transactional (propagation = Propagation.SUPPORTS, readOnly = true)
    public List <D> findAll () {
        Listable listable = Listable.builder().build();
        return this.findList(listable);
    }

    /**
     * 查找全部的数据。如果没有指定排序条件，排序的方式同{@link BaseService#findAll()}。
     *
     * @param listable
     *         搜索条件，该条件即便指定了结果集的数量，该方法始终会查询全部数据，并且按照指定的排序和搜索条件进行检索数据
     *
     * @return 对应的实体列表
     */
    @Override
    @Transactional (propagation = Propagation.SUPPORTS, readOnly = true)
    public List <D> findAll (Listable listable) {
        return this.findList(listable);
    }

    /**
     * 根据ID查询列表，该方法会校验会将重复的ID去除，使用IN查询。如果实体类继承自OrderEntity，按排序升序。否则按照ID降序
     *
     * @param ids
     *         id列表
     *
     * @return 对应的实体列表
     */
    @Override
    @Transactional (propagation = Propagation.SUPPORTS, readOnly = true)
    public List <D> findList (Long... ids) {
        // 如果没有id
        if (ArrayUtils.isEmpty(ids)) {
            return Collections.emptyList();
        }
        List <Long> idList = Arrays.asList(ids);
        return this.findList(idList);
    }

    /**
     * 根据ID查询列表，该方法会校验会将重复的ID去除，使用IN查询。如果实体类继承自OrderEntity，按排序升序。否则按照ID降序
     *
     * @param ids
     *         id列表
     *
     * @return 对应的实体列表
     */
    @Override
    @Transactional (propagation = Propagation.SUPPORTS, readOnly = true)
    public List <D> findList (List <Long> ids) {
        List <Long> idList = vip.dexian.common.utils.CollectionUtils.distinct(ids);

        if (CollectionUtils.isEmpty(idList)) {
            return Collections.emptyList();
        }

        List <Filter> filters = FilterUtils.builder().inId(idList).build();

        Listable listable = Listable.builder().filters(filters).build();

        return this.findList(listable);
    }

    /**
     * 根据起始id和结束id查找列表
     *
     * @param begin
     *         开始id
     * @param end
     *         结束id
     *
     * @return 如果 begin is not Positive or end is not Positive，则返回空
     */
    @Override
    @Transactional (propagation = Propagation.SUPPORTS, readOnly = true)
    public List <D> findList (Long begin, Long end) {

        if (!NumberUtils.isPositive(begin) || !NumberUtils.isPositive(end)) {
            return Collections.emptyList();
        }

        List <Filter> filters = FilterUtils.builder().betweenId(begin, end).build();

        Listable listable = Listable.builder().filters(filters).build();

        return this.findList(listable);
    }

    /**
     * 查找列表。如果实体类继承自OrderEntity，按排序升序。否则按照ID降序
     *
     * @param listable
     *         列表参数
     *
     * @return 查找的实体类列表
     *
     * @see Listable
     */
    @Override
    @Transactional (propagation = Propagation.SUPPORTS, readOnly = true)
    public List <D> findList (Listable listable) {
        List <T> entities = this.baseDao.findList(listable);
        return this.baseAssembler.toDTO(entities);
    }

    /**
     * 根据属性模糊查找匹配的数据，最多只查询10条
     *
     * @param property
     *         属性名称
     * @param value
     *         属性值
     *
     * @return 属性名称或值为 empty，则返回 empty list。否则返还查找到的数据
     */
    @Override
    @Transactional (propagation = Propagation.SUPPORTS, readOnly = true)
    public List <D> findByProperty (String property, String value) {

        if (StringUtils.isEmpty(property) || StringUtils.isEmpty(value)) {
            return Collections.emptyList();
        }

        List <Filter> filters = FilterUtils.builder().like(property, value).build();
        Listable listable = Listable.builder().count(10).filters(filters).build();

        return this.findList(listable);
    }


    /**
     * 分页查找数据。
     *
     * @param pageable
     *         搜索条件，包含客户端的搜索信息以及分页信息
     *
     * @return 分页数据
     */
    @Override
    @Transactional (propagation = Propagation.SUPPORTS, readOnly = true)
    public Page <D> findPage (Pageable pageable) {
        Page <T> page = this.baseDao.findPage(pageable);
        return this.baseAssembler.toDTO(page);
    }

    /**
     * 分页查找数据，当{@link BaseService#findPage(vip.dexian.common.query.Pageable)}无法满足查询需求时，可以通过该方法扩展。
     *
     * @param pageable
     *         搜索条件，包含客户端的搜索信息以及分页信息
     * @param query
     *         自定义查询条件
     * @param <Q>
     *         自定义查询条件
     *
     * @return 分页数据
     */
    @Override
    @Transactional (propagation = Propagation.SUPPORTS, readOnly = true)
    public <Q extends BaseQuery> Page <D> findPage (Pageable pageable, Q query) {
        Page <T> page = this.baseDao.findPage(pageable, query);
        return this.baseAssembler.toDTO(page);
    }

    /**
     * 根据id查找实体类是否存在
     *
     * @param id
     *         实体类id
     *
     * @return 如果id == null || 无法查找到数据，则返回false。否则返回true
     */
    @Override
    @Transactional (propagation = Propagation.SUPPORTS, readOnly = true)
    public boolean exists (ID id) {
        return this.baseDao.find(id) != null;
    }

    /**
     * 根据条件查询是否存在
     *
     * @param filters
     *         搜索条件。
     *
     * @return 指定条件下的查询出来的count>0，返回true，否则false
     */
    @Override
    @Transactional (propagation = Propagation.SUPPORTS, readOnly = true)
    public boolean exists (Filter... filters) {
        return this.baseDao.exists(Arrays.asList(filters));
    }

    /**
     * 根据条件查询是否存在
     *
     * @param filters
     *         搜索条件
     *
     * @return 指定条件下的查询出来的count>0，返回true，否则false
     */
    @Override
    @Transactional (propagation = Propagation.SUPPORTS, readOnly = true)
    public boolean exists (List <Filter> filters) {
        return this.baseDao.exists(filters);
    }


    /**
     * 保存实体类。持久化数据到数据库， 持久化完毕之后，实体类为托管状态。在同一个事务中，如果对该实体类进行属性修改，会发出update语句
     *
     * @param entity
     *         实体类
     *
     * @return 保存后的entity
     */
    @Override
    @Transactional (rollbackFor = Exception.class)
    public T save (T entity) {
        Assert.notNull(entity, "实体类为空");
        Assert.isTrue(entity.isNew(), "实体类的ID不为空");

        this.baseDao.persist(entity);

        return entity;
    }


    /**
     * 更新实体类，如果实体类处于托管状态。则不进行查询，否则将会执行一次查询将实体类纳入持久化上下文管理，然后根据属性是否修改发出update语句
     *
     * @param entity
     *         实体类
     *
     * @return 更新后的entity
     */
    @Override
    @Transactional (rollbackFor = Exception.class)
    public T update (T entity) {
        return this.update(entity, true);
    }

    /**
     * 更新实体类，如果实体类处于托管状态，则不进行查询，否则将会执行一次查询将实体类纳入持久化上下文管理，然后根据属性是否修改发出update语句
     *
     * @param entity
     *         实体类
     * @param ignoreNullProperty
     *         是否忽略null值的更新（启用null值也更新）
     *
     * @return 更新后的entity
     */
    @Override
    @Transactional (rollbackFor = Exception.class)
    public T update (T entity, boolean ignoreNullProperty) {
        Assert.notNull(entity, "实体类为空");
        Assert.isTrue(!entity.isNew(), "id为null");

        // 不处于托管状态
        if (!this.baseDao.isManaged(entity)) {
            T persistant = this.baseDao.find(this.baseDao.getIdentifier(entity));
            if (persistant != null) {
                this.copyProperties(entity, persistant, ignoreNullProperty, UPDATE_IGNORE_PROPERTIES);
            }
            return persistant;
        }
        return entity;
    }

    /**
     * 更新实体类。当实体类处于托管状态的时候，不能调用该方法。该方法会根据实体类的id进行一次查询，然后进行属性的复制操作，发现属性变更，则进行更新。否则不执行更新操作
     *
     * @param entity
     *         实体类
     * @param ignoreProperties
     *         忽略的属性
     *
     * @return 更新后的实体类
     */
    @Override
    @Transactional (rollbackFor = Exception.class)
    public T update (T entity, String... ignoreProperties) {
        return this.update(entity, true, ignoreProperties);
    }

    /**
     * 更新实体类。当实体类处于托管状态的时候，不能调用该方法。该方法会根据实体类的id进行一次查询，然后进行属性的复制操作，发现属性变更，则进行更新。否则不执行更新操作
     *
     * @param entity
     *         实体类。不可处于托管状态（即调用isManaged方法返回false）-> 游离状态。
     * @param ignoreNullProperty
     *         是否忽略null值的更新
     * @param ignoreProperties
     *         忽略的属性
     *
     * @return 更新后的entity
     */
    @Override
    @Transactional (rollbackFor = Exception.class)
    public T update (T entity, boolean ignoreNullProperty, String... ignoreProperties) {
        Assert.notNull(entity, "实体类为空");
        Assert.isTrue(!entity.isNew(), "实体类id为null");
        Assert.isTrue(!this.baseDao.isManaged(entity), "当前实体类属于托管状态");

        // 从数据库中查找对象（处于托管状态）
        T persistant = this.baseDao.find(this.baseDao.getIdentifier(entity));

        if (persistant != null) {
            // 复制属性
            this.copyProperties(entity, persistant, ignoreNullProperty,
                    ArrayUtils.addAll(ignoreProperties, UPDATE_IGNORE_PROPERTIES)
            );
        }
        return this.update(persistant);
    }

    /**
     * 根据ID删除实体类。该方法会先查询，如果存在就进行删除。默认执行的是“逻辑删除(更新isValid字段为false)”
     *
     * @param id
     *         实体类ID
     */
    @Override
    @Transactional (rollbackFor = Exception.class)
    public void delete (ID id) {
        this.delete(this.baseDao.find(id));
    }

    /**
     * 根据id批量删除实体类
     *
     * @param ids
     *         id列表
     */
    @Override
    @Transactional (rollbackFor = Exception.class)
    public void delete (ID... ids) {
        this.validDelete(ids);

        for (ID id : ids) {
            this.delete(id);
        }
    }

    /**
     * 根据id批量删除实体类
     *
     * @param ids
     *         id列表
     */
    @Override
    @Transactional (rollbackFor = Exception.class)
    public void delete (List <ID> ids) {
        this.validDelete(ids);

        for (ID id : ids) {
            this.delete(id);
        }
    }

    /**
     * 根据id删除实体类。该方法会先查询，如果存在就进行删除。isRealDelete==true,执行物理删除。否则执行逻辑删除
     *
     * @param id
     *         实体类id
     * @param isRealDelete
     *         是否是物理删除
     */
    @Override
    @Transactional (rollbackFor = Exception.class)
    public void delete (ID id, boolean isRealDelete) {
        this.delete(this.baseDao.find(id), isRealDelete);
    }

    /**
     * 删除实体类。如果实体类不处于上下文管理状态，则通过实体类的ID重新从数据库中读取，然后执行删除。 默认执行的是“逻辑删除(更新isValid字段为false)”
     *
     * @param entity
     *         实体类
     */
    @Override
    @Transactional (rollbackFor = Exception.class)
    public void delete (T entity) {
        this.delete(entity, false);
    }

    /**
     * 删除实体类。如果实体类不处于上下文管理状态，则通过实体类的ID重新从数据库中读取，然后执行删除。 isRealDelete==true,执行物理删除。否则执行逻辑删除
     *
     * @param entity
     *         实体类
     * @param isRealDelete
     *         是否是物理删除
     */
    @Override
    @Transactional (rollbackFor = Exception.class)
    public void delete (T entity, boolean isRealDelete) {

        // 如果实体类为空或者没有传递ID，则不进行删除
        if (entity == null || entity.isNew()) {
            return;
        }

        // 如果是物理删除
        if (isRealDelete) {
            this.baseDao.remove(
                    this.baseDao.isManaged(entity) ? entity : this.baseDao.find(this.baseDao.getIdentifier(entity)));
            return;
        }

        // 设置为无效状态
        entity.setIsValid(false);

        if (!this.baseDao.isManaged(entity)) {
            // 从数据库中查找对象（处于托管状态）
            T persistant = this.baseDao.find(this.baseDao.getIdentifier(entity));

            if (persistant != null) {
                // 复制属性
                this.copyProperties(entity, persistant, ArrayUtils.addAll(DELETE_IGNORE_PROPERTY));
            }
        }
    }

    /**
     * 复制属性，
     *
     * @param source
     *         源对象
     * @param target
     *         目标对象
     * @param ignoreProperties
     *         忽略更新的属性
     */
    protected void copyProperties (T source, T target, String... ignoreProperties) {
        this.copyProperties(source, target, true, ignoreProperties);
    }

    /**
     * 复制属性，如果isIgnoreNullProperty == true，将不更新null值得属性（需要在实体类中配合@DynamicUpdate注解使用）->实现部分属性的更新
     *
     * @param source
     *         源对象
     * @param target
     *         目标对象（托管状态）
     * @param isIgnoreNullProperty
     *         是否忽略空值的属性，如果是，将不复制该属性
     * @param ignoreProperties
     *         忽略更新的属性名称
     */
    protected void copyProperties (T source, T target, boolean isIgnoreNullProperty, String... ignoreProperties) {

        Assert.notNull(source, "源对象不能为空");
        Assert.notNull(target, "目标对象不能为空");

        log.debug("启用部分属性更新：{}", isIgnoreNullProperty);

        // 获取目标对象的方法描述符
        PropertyDescriptor[] propertyDescriptors = PropertyUtils.getPropertyDescriptors(target);

        for (PropertyDescriptor propertyDescriptor : propertyDescriptors) {

            // 属性名称
            String propertyName = propertyDescriptor.getName();
            // 读取方法
            Method readMethod = propertyDescriptor.getReadMethod();
            // 写入方法
            Method writeMethod = propertyDescriptor.getWriteMethod();

            if (ArrayUtils.contains(ignoreProperties,
                    propertyName
            ) || readMethod == null || writeMethod == null || !this.baseDao.isLoaded(source, propertyName)) {
                continue;
            }

            try {
                // 从源对象中获取值
                Object sourceValue = readMethod.invoke(source);
                // 如果没有忽略空值属性 并且 值不为空，设置值到target对象
                if (!isIgnoreNullProperty || sourceValue != null) {
                    writeMethod.invoke(target, sourceValue);
                }

            } catch (IllegalAccessException | InvocationTargetException e) {
                throw new ServiceValidationException(e.getMessage(), e);
            }
        }
    }

    /**
     * 校验删除的数量。如果超出 {@link BaseServiceImpl#MAX_DELETE_COUNT} ，则抛出异常
     *
     * @param ids
     *         id列表
     *
     * @throws ServiceValidationException
     *         ids的数量超过 {@link BaseServiceImpl#MAX_DELETE_COUNT}
     */
    protected void validDelete (ID... ids) {
        if (ArrayUtils.isEmpty(ids)) {
            return;
        }

        if (ids.length > MAX_DELETE_COUNT) {
            throw new ServiceValidationException("00004", MAX_DELETE_COUNT);
        }
    }

    /**
     * 校验删除的数量。如果超出 {@link BaseServiceImpl#MAX_DELETE_COUNT} ，则抛出异常
     *
     * @param ids
     *         id列表
     *
     * @throws ServiceValidationException
     *         ids的数量超过 {@link BaseServiceImpl#MAX_DELETE_COUNT}
     */
    protected void validDelete (List <ID> ids) {

        if (CollectionUtils.isEmpty(ids)) {
            return;
        }

        if (ids.size() > MAX_DELETE_COUNT) {
            throw new ServiceValidationException("00004", MAX_DELETE_COUNT);
        }
    }

}
